package root.report.util.budget;

import java.io.UnsupportedEncodingException;

public class Base64Helper {

	// base 64 character set
	//
	private static final char[] BCS = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
			'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
			'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };

	// base 64 wadding
	private static final char PAD = '=';

	// size of base 64 decode table
	private static final int BDTSIZE = 128;

	// base 64 decode table  
	private static final byte[] BDT = new byte[128];

	private static final int PAD_SIZE0 = 1;
	private static final int PAD_SIZE4 = 2;
	private static final int PAD_SIZE8 = 3;

	// class static intializer for building decode table
	static {
		for (int i = 0; i < BDTSIZE; i++) {
			BDT[i] = Byte.MAX_VALUE;
		}

		for (int i = 0; i < BCS.length; i++) {
			BDT[BCS[i]] = (byte) i;
		}
	}

	/**
	 * The <code>decode_chunk</code> routine decodes a chunk of data
	 * into its native encoding.
	 *
	 * base64 encodes each 3 octets of data into 4 characters from a
	 * limited 64 character set. The 3 octets are joined to form
	 * 24 bits which are then split into 4 x 6bit values. Each 6 bit
	 * value is then used as an index into the 64 character table of
	 * base64 chars. If the total data length is not a 3 octet multiple
	 * the '=' char is used as padding for the final 4 char group, 
	 * either 1 octet + '==' or 2 octets + '='.
	 *
	 * @param   id  The input data to be processed
	 * @param   o   The offset from which to begin processing
	 * @param   l   The length (bound) at which processing is to end
	 * @return  The decoded data   
	 * formatting exceptions in the encoded data 
	 * @throws Exception 
	 */
	private static byte[] decodeChunk(char[] id, int o, int l) throws Exception {
		// Keep it simple - must be >= 4. Unpadded
		// base64 data contain < 3 octets is invalid.
		//
		if ((l - o) < 4)
			return null;
		char[] ib = new char[4];
		int ibcount = 0;
		// cryan. Calc the num of octets. Each 4 chars of base64 chars
		// (representing 24 bits) encodes 3 octets. 
		//
		int octetCount = 3 * (l / 4);
		// Final 4 chars may contain 3 octets or padded to contain
		// 1 or 2 octets.
		//
		if (id[l - 1] == PAD)
			// TT== means last 4 chars encode 8 bits (ie subtract 2)
			// TTT= means last 4 chars encode 16 bits (ie subtract 1)
			octetCount -= (id[l - 2] == PAD) ? 2 : 1;
		byte[] ob = new byte[octetCount];
		int obcount = 0;
		for (int i = o; i < o + l && i < id.length; i++) {
			if (id[i] == PAD || id[i] < BDT.length && BDT[id[i]] != Byte.MAX_VALUE) {
				ib[ibcount++] = id[i];
				// Decode each 4 char sequence.
				//
				if (ibcount == ib.length) {
					ibcount = 0;
					obcount += processEncodeme(ib, ob, obcount);
				}
			}
		}

		if (obcount != ob.length) {
			byte[] tmp = new byte[obcount];
			System.arraycopy(ob, 0, tmp, 0, obcount);
			ob = tmp;
		}
		return ob;
	}

	public static byte[] decode(String id) throws Exception {
		char[] cd = id.toCharArray();
		return decodeChunk(cd, 0, cd.length);
	}

	public static String decodeStr(String str) throws Exception {
		try {
			return new String(decode(str), "UTF-8");
		}
		catch (UnsupportedEncodingException e) {
			throw new Exception(e);
		}
	}

	public static String encodeStr(String str) throws Exception {
		try {
			return encode(str.getBytes("UTF-8"));
		}
		catch (UnsupportedEncodingException e) {
			throw new Exception(e);
		}
	}

	// Returns base64 representation of specified byte array.
	//
	public static String encode(byte[] id) {
		char[] cd = encodeChunk(id, 0, id.length);
		return new String(cd, 0, cd.length);
	}

	// Returns base64 representation of specified byte array.
	//
	private static char[] encodeChunk(byte[] id, int o, int l) {
		if (l <= 0)
			return null;
		char[] out;
		// If not a multiple of 3 octets then a final padded 4 char
		// slot is needed.
		//
		if ((l - o) % 3 == 0)
			out = new char[l / 3 * 4];
		else
			out = new char[l / 3 * 4 + 4];
		int rindex = o;
		int windex = 0;
		int rest = l - o;
		while (rest >= 3) {
			int i = ((id[rindex] & 0xff) << 16) + ((id[rindex + 1] & 0xff) << 8) + (id[rindex + 2] & 0xff);
			out[windex++] = BCS[i >> 18];
			out[windex++] = BCS[(i >> 12) & 0x3f];
			out[windex++] = BCS[(i >> 6) & 0x3f];
			out[windex++] = BCS[i & 0x3f];
			rindex += 3;
			rest -= 3;
		}
		if (rest == 1) {
			int i = id[rindex] & 0xff;
			out[windex++] = BCS[i >> 2];
			out[windex++] = BCS[(i << 4) & 0x3f];
			out[windex++] = PAD;
			out[windex++] = PAD;
		}
		else if (rest == 2) {
			int i = ((id[rindex] & 0xff) << 8) + (id[rindex + 1] & 0xff);
			out[windex++] = BCS[i >> 10];
			out[windex++] = BCS[(i >> 4) & 0x3f];
			out[windex++] = BCS[(i << 2) & 0x3f];
			out[windex++] = PAD;
		}
		return out;
	}

	/**
	 * The <code>process</code> routine processes an atomic base64
	 * unit of encoding (encodeme) into its native encoding. This class is
	 * used by decode routines to do the grunt work of decoding
	 * base64 encoded information
	 *
	 * @param   ib  Input character buffer of encoded bytes
	 * @param   ob  Output byte buffer of decoded bytes
	 * @param   p   Pointer to the encodeme of interest
	 * @return  The decoded encodeme
	 * formatting exceptions in the encoded data 
	 * @throws Exception 
	 */
	private static int processEncodeme(char[] ib, byte[] ob, int p) throws Exception {
		int spad = PAD_SIZE8;
		if (ib[3] == PAD)
			spad = PAD_SIZE4;
		if (ib[2] == PAD)
			spad = PAD_SIZE0;
		int b0 = BDT[ib[0]];
		int b1 = BDT[ib[1]];
		int b2 = BDT[ib[2]];
		int b3 = BDT[ib[3]];
		switch (spad) {
			case PAD_SIZE0:
				ob[p] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
				return PAD_SIZE0;
			case PAD_SIZE4:
				ob[p++] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
				ob[p] = (byte) (b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
				return PAD_SIZE4;
			case PAD_SIZE8:
				ob[p++] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
				ob[p++] = (byte) (b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
				ob[p] = (byte) (b2 << 6 & 0xc0 | b3 & 0x3f);
				return PAD_SIZE8;
			default:
				throw new Exception("δ֪���");
		}
	}
}